package edu.thu.thss.rsa.yxy;

import java.math.BigInteger;
import java.util.Random;

/**
 * Standard Implementation of Rivest-Shamir-Adleman(RSA)
 * 
 * @author Andy Gao
 *
 */
public class RSACryptography {
	private final BigInteger TWO = new BigInteger("2");
	private final BigInteger THREE = new BigInteger("3");

	private BigInteger p; // first prime
	private BigInteger q; // second prime

	public PublicKey publicKey;
	private PrivateKey privateKey;
	
	public PublicKeyFast publicKeyFast;
	private PrivateKeyFast privateKeyFast;

	
	public RSACryptography() {
		super();
	}

	public RSACryptography(int primeSize, Random random){
		initial(primeSize, random);
	}

	/**
	 * 初始化两个强素数，并生成公钥和密钥
	 * 
	 * @param primeSize
	 *            素数的位数
	 * @param random
	 *            随机数产生器
	 */
	public void initial(int primeSize, Random random) {
		int size1 = primeSize / 2;
		int size2 = size1;
		int offset1 = (int) (5.0 * (random.nextDouble()) + 5.0);
		int offset2 = -offset1;
		if (random.nextDouble() < 0.5) {
			offset1 = -offset1;
			offset2 = -offset2;
		}
		size1 += offset1;
		size2 += offset2;

		// generate two random primes, so that p*q = n has size bits
		BigInteger p1 = new BigInteger(size1, random);
		p = nextPrime(p1);
		BigInteger q1 = new BigInteger(size2, random);
		q = nextPrime(q1);
		// calculate n=pq
		BigInteger n = p.multiply(q);
		publicKey = new PublicKey(n);
		publicKeyFast = new PublicKeyFast(n);
		// calculate phi(n)=(p-1)(q-1)
		BigInteger pM1 = p.subtract(BigInteger.ONE);
		BigInteger qM1 = q.subtract(BigInteger.ONE);
		BigInteger phiN = pM1.multiply(qM1); // (p-1)*(q-1)
		BigInteger d = publicKey.getE().modInverse(phiN);
		privateKey = new PrivateKey(d, n);
		privateKeyFast = new PrivateKeyFast(d, p , q, n);
	}

	// nextPrime: next prime p after x, with p-1 and 3 relatively prime
	public BigInteger nextPrime(BigInteger x) {
		if ((x.remainder(TWO)).equals(BigInteger.ZERO))
			x = x.add(BigInteger.ONE);
		while (true) {
			BigInteger xM1 = x.subtract(BigInteger.ONE);
			if (!(xM1.remainder(THREE)).equals(BigInteger.ZERO))
				if (x.isProbablePrime(10))
					break;
			x = x.add(TWO);
		}
		return x;
	}

	public PublicKey getPublicKey() {
		return publicKey;
	}

	public PrivateKey getPrivateKey() {
		return privateKey;
	}

	public PublicKeyFast getPublicKeyFast() {
		return publicKeyFast;
	}

	public PrivateKeyFast getPrivateKeyFast() {
		return privateKeyFast;
	}
}
